﻿using System;
using System.Collections.Specialized;
using System.Data.Services.Client;
using System.Globalization;
using System.IO;
using System.Net;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Security.Cryptography.X509Certificates;
using bdtheque.azure.business.acs.management;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace bdtheque.azure.business.appfabric
{
    /// <summary>
    /// This class obtains a SWT token and adds it to the HTTP authorize header 
    /// for every request to the management service.
    /// </summary>
    public class AcsManagementServiceHelper
    {
        static string cachedSwtToken;

        /// <summary>
        /// Creates and returns a ManagementService object. This is the only 'interface' used by other classes.
        /// </summary>
        /// <returns>An instance of the ManagementService.</returns>
        public static ManagementService CreateManagementServiceClient()
        {
            string acsServiceNamespace = RoleEnvironment.GetConfigurationSettingValue("AcsServiceNamespace");
            string acsHostUrl = RoleEnvironment.GetConfigurationSettingValue("AcsHostUrl");
            string acsManagementServicesRelativeUrl = RoleEnvironment.GetConfigurationSettingValue("AcsManagementServicesRelativeUrl");

            string managementServiceEndpoint = String.Format(CultureInfo.InvariantCulture, "https://{0}.{1}/{2}", acsServiceNamespace, acsHostUrl, acsManagementServicesRelativeUrl);
            var managementService = new ManagementService(new Uri(managementServiceEndpoint));

            managementService.SendingRequest += GetTokenWithWritePermission;

            return managementService;
        }

        /// <summary>
        /// Event handler for getting a token from ACS.
        /// </summary>
        /// <param name="sender">Sender of the event.</param>
        /// <param name="args">Event arguments.</param>
        public static void GetTokenWithWritePermission(object sender, SendingRequestEventArgs args)
        {
            GetTokenWithWritePermission((HttpWebRequest) args.Request);
        }

        /// <summary>
        /// Helper function for the event handler above, adding the SWT token to the HTTP 'Authorization' header. 
        /// The SWT token is cached so that we don't need to obtain a token on every request.
        /// </summary>
        /// <param name="args">Event arguments.</param>
        public static void GetTokenWithWritePermission(HttpWebRequest args)
        {
            if (cachedSwtToken == null)
            {
                cachedSwtToken = GetTokenFromACS();
            }

            args.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + cachedSwtToken);
        }

        /// <summary>
        /// Obtains a SWT token from ACSv2. 
        /// </summary>
        /// <returns>A token  from ACS.</returns>
        static string GetTokenFromACS()
        {
            string acsServiceNamespace = RoleEnvironment.GetConfigurationSettingValue("AcsServiceNamespace");
            string acsHostUrl = RoleEnvironment.GetConfigurationSettingValue("AcsHostUrl");
            string acsManagementServicesRelativeUrl = RoleEnvironment.GetConfigurationSettingValue("AcsManagementServicesRelativeUrl");
            string acsManagementServiceIdentityName = RoleEnvironment.GetConfigurationSettingValue("AcsManagementServiceIdentityName");
            string acsManagementServiceIdentityKey = RoleEnvironment.GetConfigurationSettingValue("AcsManagementServiceIdentityKey");
            
            //
            // Request a token from ACS
            //
            var client = new WebClient();
            client.BaseAddress = string.Format(CultureInfo.CurrentCulture, "https://{0}.{1}", acsServiceNamespace, acsHostUrl);

            var values = new NameValueCollection();
            values.Add("grant_type", "client_credentials");
            values.Add("client_id", acsManagementServiceIdentityName);
            values.Add("client_secret", acsManagementServiceIdentityKey);
            values.Add("scope", client.BaseAddress + acsManagementServicesRelativeUrl);

            byte[] responseBytes = client.UploadValues("/v2/OAuth2-13", "POST", values);

            //
            // Extract the access token and return it.
            //
            using(var responseStream = new MemoryStream(responseBytes))
            {
                var tokenResponse = (OAuth2TokenResponse) new DataContractJsonSerializer(typeof(OAuth2TokenResponse)).ReadObject(responseStream);
                return tokenResponse.access_token;
            }
        }

        /// <summary>
        /// Helper function to read the content of a .pfx file to a byte array. 
        /// </summary>
        public static byte[] ReadBytesFromPfxFile(string pfxFileName, string protectionPassword)
        {
            //
            // Read the bytes from the .pfx file.
            //
            byte[] signingCertificate;
            using (var stream = File.OpenRead(pfxFileName))
            {
                using (var br = new BinaryReader(stream))
                {
                    signingCertificate = br.ReadBytes((int) stream.Length);
                }
            }

            //
            // Double check on the read byte array by creating a X509Certificate2 object which should not throw.
            //
            var cert = new X509Certificate2(signingCertificate, protectionPassword);

            if (!cert.HasPrivateKey)
            {
                throw new InvalidDataException(pfxFileName + "doesn't have a private key.");
            }

            return signingCertificate;
        }

        [DataContract]
        private class OAuth2TokenResponse
        {
            [DataMember]
            public string access_token;
        }
    }
}

